﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SQLite;
using System.Linq;
using Director.DomainModels;
using TmntsCommon;

namespace Director
{
	class DirectorStorage : AbstractSqLiteStorage
	{
		private readonly Dictionary<long,string> _registeredOrders = new Dictionary<long, string>();

		public DirectorStorage()
			: base(ConfigurationManager.AppSettings["StoragePath" + Environment.MachineName])
		{
			CreateCommand(null, "CREATE TABLE IF NOT EXISTS positions ( " +
								"seccode TEXT PRIMARY KEY, saldo SINGLE, averageprice SINGLE)").ExecuteNonQuery();

			CreateCommand(null, @"CREATE TABLE IF NOT EXISTS trades ( 
											tradeno INTEGER PRIMARY KEY,
											orderno INTEGER,
											seccode TEXT,
											buysell TEXT,
											time DATETIME,
											comission SINGLE,
											price SINGLE,
											quantity INTEGER,
											currentpos INTEGER)").ExecuteNonQuery();

			CreateCommand(null, @"CREATE TABLE IF NOT EXISTS orders ( 
											orderno INTEGER,
											seccode TEXT,
											buysell TEXT,
											time DATETIME,
											quantity INTEGER)").ExecuteNonQuery();

			CreateCommand(null, @"CREATE TABLE IF NOT EXISTS stopLoss ( 
											seccode TEXT,
											time DATETIME,
											price SINGLE)").ExecuteNonQuery();

			CreateCommand(null, @"CREATE TABLE IF NOT EXISTS lastLeader (seccode TEXT)").ExecuteNonQuery();
		}

		public void SaveStopLoss(Security security, decimal price)
		{
			string update = "INSERT INTO [stopLoss] (seccode,time,price) VALUES (@seccode,@time,@price)";

			if (ReadStopLoss(security) != 0)
			{
				update = "UPDATE [stopLoss] SET price=@price WHERE seccode=@seccode;";
			}

			ExecuteInsertOrUpdate(null, update,
				new SQLiteParameter("@seccode", DbType.String) {Value = security.Code},
				new SQLiteParameter("@time", DbType.DateTime) {Value = DateTime.Now},
				new SQLiteParameter("@price", DbType.Decimal) {Value = price});
		}

		public decimal ReadStopLoss(Security security)
		{
			var cmd = Connection.CreateCommand();
			cmd.CommandText = "SELECT price FROM [stopLoss] WHERE seccode='" + security.Code + "'";
			using (var l = cmd.ExecuteReader())
			{
				if(l.Read()) return l.GetDecimal(0);
			}
			return 0.0m;
		}

		public void WriteTrade(Trade trade)
		{
			{
				var cmd = Connection.CreateCommand();
				cmd.CommandText = "SELECT tradeno FROM [trades] WHERE tradeno=" + trade.TradeNo;
				var l = cmd.ExecuteScalar();
				if (l != DBNull.Value && l != null)
				{
					return;
				}
			}
			{
				ExecuteInsertOrUpdate(null, "INSERT INTO [trades] (tradeno,orderno,seccode,buysell,time,comission,price,quantity,currentpos) VALUES (@tradeno,@orderno,@seccode,@buysell,@time,@comission,@price,@quantity,@currentpos);",
						new SQLiteParameter("@tradeno", DbType.Int64) { Value = trade.TradeNo },
						new SQLiteParameter("@orderno", DbType.Int64) { Value = trade.OrderNo },
						new SQLiteParameter("@seccode", DbType.String) { Value = trade.SecCode },
						new SQLiteParameter("@buysell", DbType.String) { Value = trade.IsBuy?"B":"S" },
						new SQLiteParameter("@time", DbType.DateTime) { Value = trade.Time },
						new SQLiteParameter("@price", DbType.Decimal) { Value = trade.Price },
						new SQLiteParameter("@quantity", DbType.Int32) { Value = trade.Quantity },
						new SQLiteParameter("@comission", DbType.Decimal) { Value = trade.Comission },
						new SQLiteParameter("@currentpos", DbType.Int32) { Value = trade.Currentpos});
			}
		}

		public void WriteOrder(Order order)
		{
			{
				var cmd = Connection.CreateCommand();
				cmd.CommandText = "SELECT orderno FROM [orders] WHERE orderno=" + order.OrderNo;
				var l = cmd.ExecuteScalar();
				if (l != DBNull.Value && l != null)
				{
					return;
				}
			}
			{
				string source;
				if (!_registeredOrders.TryGetValue(order.OrderNo, out source))
				{
					source = "UNKNOWN";
				}

				ExecuteInsertOrUpdate(null, "INSERT INTO [orders] (orderno,seccode,buysell,time,quantity) VALUES (@orderno,@seccode,@buysell,@time,@quantity);",
						new SQLiteParameter("@orderno", DbType.Int64) { Value = order.OrderNo },
						new SQLiteParameter("@seccode", DbType.String) { Value = order.SecCode },
						new SQLiteParameter("@buysell", DbType.String) { Value = order.IsBuy ? "B" : "S" },
						new SQLiteParameter("@time", DbType.DateTime) { Value = order.Time },
						new SQLiteParameter("@quantity", DbType.Int32) { Value = order.Quantity });
			}
		}

		public decimal GetAveragePrice(string secCode)
		{
			var cmd = Connection.CreateCommand();
			cmd.CommandText = "SELECT buysell,price,quantity,currentpos FROM [trades] WHERE seccode='" + secCode + "' order by time";
			using(var rdr = cmd.ExecuteReader(CommandBehavior.Default))
			{
				var result = 0m;
				while (rdr.Read())
				{
					if (rdr.GetString(0) == "B")
					{
						var price = rdr.GetDecimal(1);
						var quantity = rdr.GetInt32(2);
						var currentpos = rdr.GetInt64(3);

						if (currentpos == quantity)
						{
							result = price;
						}
						else
						{
							var before = currentpos - quantity;
							if (before <= 0)
							{
								throw new Exception("Неведомая херня");
							}

							result = (before*result + quantity*price)/currentpos;
						}
					}
				}
				return result;
			}
		}

		public decimal CalculatePnL(Security security)
		{
			var avPrice = 0m;
			var pnl = 0m;

			var cmd = Connection.CreateCommand();
			cmd.CommandText = "SELECT buysell,price,quantity,currentpos FROM [trades] WHERE seccode='" + security.Code + "' order by time";
			using (var rdr = cmd.ExecuteReader(CommandBehavior.Default))
			{
				while (rdr.Read())
				{
					var price = rdr.GetDecimal(1);
					var quantity = rdr.GetInt32(2);
					var currentpos = rdr.GetInt64(3);

					if (rdr.GetString(0) == "B")
					{
						if (currentpos == quantity)
						{
							avPrice = price;
						}
						else
						{
							var before = currentpos - quantity;
							if (before <= 0)
							{
								throw new Exception("Неведомая херня");
							}

							avPrice = (before*avPrice + quantity*price)/currentpos;
						}
					}
					else
					{
						pnl += quantity * security.Lotsize * (price - avPrice);
					}

					//комиссия
					pnl -= quantity * security.Lotsize * price * 0.03m / 100;
				}
			}

			return pnl;
		}

		public void SetLeader(Security security)
		{
			ExecuteInsertOrUpdate(null, "DELETE FROM lastLeader");
			if (security != null)
			{
				ExecuteInsertOrUpdate(null, "INSERT INTO [lastLeader] (seccode) VALUES (@seccode);", new SQLiteParameter("@seccode", DbType.String) {Value = security.Code});
			}
		}

		public string GetLeader()
		{
			var cmd = Connection.CreateCommand();
			cmd.CommandText = "SELECT seccode FROM [lastLeader]";
			using (var rdr = cmd.ExecuteReader(CommandBehavior.Default))
			{
				while (rdr.Read())
				{
					return rdr.GetString(0);
				}
			}

			return null;
		}

		public void RegisterOrder(long id, string source)
		{
			_registeredOrders[id] = source;
		}

		public void ReadAllOrders()
		{
			var cmd = Connection.CreateCommand();
			cmd.CommandText = "SELECT orderno, seccode, buysell, time, quantity FROM [orders]";
			using (var rdr = cmd.ExecuteReader(CommandBehavior.Default))
			{
				while (rdr.Read())
				{
					OrdersRegistrator.Instance.RegisterOrder(new Order
					{
						                                         IsBuy = rdr.GetString(2)=="B",
						                                         OrderNo = rdr.GetInt64(0),
						                                         Quantity = rdr.GetInt32(4),
						                                         SecCode = rdr.GetString(1),
						                                         Time = rdr.GetDateTime(3)
					                                         });
				}
			}
		}

		public decimal? GetAveragePriceByOrder(long orderNo)
		{
			var trades = new List<Tuple<int, decimal>>();
			var cmd = Connection.CreateCommand();
			cmd.CommandText = "SELECT buysell,price,quantity,currentpos FROM [trades] WHERE orderno=" + orderNo + " order by time";
			using (var rdr = cmd.ExecuteReader(CommandBehavior.Default))
			{
				while (rdr.Read())
				{
					var price = rdr.GetDecimal(1);
					var quantity = rdr.GetInt32(2);
					trades.Add(Tuple.Create(quantity, price));
				}
				if (trades.Count == 0)
				{
					return null;
				}
				return trades.Sum(e => e.Item1*e.Item2)/trades.Sum(e => e.Item1);
			}
		}
	}
}
